package org.microboot.validator.aspect;

import com.google.common.collect.Maps;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.ValidatorException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.microboot.core.bean.ApplicationContextHolder;
import org.microboot.core.constant.Constant;
import org.microboot.validator.annotation.Validator;
import org.microboot.validator.entity.ValidatorEntity;
import org.microboot.validator.func.ValidatorFunc;
import org.microboot.validator.resolver.ValidatorResolver;
import org.microboot.web.utils.RequestUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * @author 胡鹏
 */
@Aspect
/*
    为什么这里@Order注解的值指定为2？
        1、希望ValidatorAspect在LoggingAspect后面执行（LoggingAspect中@Order指定的是0）
        2、如果有其他自定义的Aspect希望介于LoggingAspect和ValidatorAspect之间执行时，可以指定@Order的值为1
 */
@Order(2)
public class ValidatorAspect {

    //模仿Spring和SpringMVC源码，给validatorName一个缓存，不用每次都去解析注解
    private final Map<Method, String> validatorNameCache = Maps.newConcurrentMap();

    /**
     * 说明1：@annotation只能针对方法，为了增加在类上使用注解的功能，考虑使用@whith或@target
     *
     * 说明2：假设执行son.f()
     * 1、如果是@target(A)：会查看调用f()方法的son对象的S类上是否有注解A
     * 2、如果是@withn(A)：会查看f()方法所属的P类上是否有注解A
     * 总结：关键在于：一个针对的是对象，一个针对的是所属的类
     * 参考：https://blog.csdn.net/hikisan/article/details/118676615
     *
     * 上面是网上资料，我自己做了两个实验，分别在Spring和SpringBoot环境下
     * 1、Spring环境下的实验中，@target大多时候会不生效
     * 2、SpringBoot环境下的实验中，@target会报BeanCreationException异常
     * 实验1在mashibing-spring项目的com.mashibing.spring.test.aop.scan.annotation包下
     * 实验2在microboot-test-web6项目下
     * 参考：https://blog.csdn.net/hello_qaz123/article/details/131819865
     *
     * 因此最终决定使用@within
     */
    @Pointcut("@annotation(org.microboot.validator.annotation.Validator) || @within(org.microboot.validator.annotation.Validator)")
    public void cut() {
    }

    @SuppressWarnings("unchecked")
    @Around("cut()")
    public Object aroundAspect(final ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //获取Method
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取校验文件名称
        String validatorName = validatorNameCache.get(method);
        if (StringUtils.isBlank(validatorName)) {
            //获取Method所属的Class
            Class clazz = method.getDeclaringClass();
            //获取Method上的Validator注解
            Validator validatorByMethod = AnnotationUtils.findAnnotation(method, Validator.class);
            //获取Class上的Validator注解
            Validator validatorByClass = AnnotationUtils.findAnnotation(clazz, Validator.class);
            //缓存method与校验文件名称的关系，使用computeIfAbsent是为了线程安全
            validatorName = validatorNameCache.computeIfAbsent(method,
                    m -> validatorByMethod != null ? validatorByMethod.value() : validatorByClass.value());
        }
        //获取校验规则集合
        List<ValidatorEntity> validatorEntityList = ApplicationContextHolder.getBean(ValidatorResolver.class).getValidatorEntityList(validatorName);
        //声明currField，用于记录属性名
        String currField = null;
        //声明currValue，用于记录属性值
        String currValue = null;
        //遍历校验规则集合
        for (ValidatorEntity validatorEntity : validatorEntityList) {
            String field = validatorEntity.getField();
            String message = validatorEntity.getMessage();
            Map<String, Object> validator = validatorEntity.getValidator();
            ValidatorFunc validatorFunc = validatorEntity.getValidatorFunc();
            if (StringUtils.isBlank(currField) || !StringUtils.equals(currField, field)) {
                //获取属性名
                currField = field;
                //获取属性值
                currValue = RequestUtils.getParameter(request, currField);
            }
            Map<String, Object> result;
            try {
                //对请求值进行校验
                result = validatorFunc.validate(validator, message, currField, currValue, request);
            } catch (Exception e) {
                throw new ValidatorException(e.getMessage());
            }
            if (result != null) {
                String code = MapUtils.getString(result, "code");
                if (StringUtils.equals(Constant.CODE_0, code)) {
                    throw new ValidatorException(MapUtils.getString(result, "msg", Constant.ERROR_4));
                }
            }
        }
        //调用执行目标方法，obj就是方法返回值
        return joinPoint.proceed(joinPoint.getArgs());
    }
}
